Skip to content

ffi: add new methods and error codes#62762

Open
ShogunPanda wants to merge 2 commits intonodejs:mainfrom
ShogunPanda:ffi-followup
Open

ffi: add new methods and error codes#62762
ShogunPanda wants to merge 2 commits intonodejs:mainfrom
ShogunPanda:ffi-followup

Conversation

@ShogunPanda
Copy link
Copy Markdown
Contributor

As promised to @justjake, also the following followups requested in #62072 (comment):

  • Added ffi.exportArrayBuffer(...) and ffi.exportArrayBufferView(...).
  • Added raw pointer helper ffi.getRawPointer(source) (Buffer | ArrayBuffer | ArrayBufferView), with strong “unsafe/dangerous” docs and tests.
  • Moved byte-export copying to native code (exportBytes) to avoid extra JS Buffer wrapper allocations.

@ShogunPanda ShogunPanda requested review from bengl and jasnell April 15, 2026 18:28
@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/config
  • @nodejs/gyp
  • @nodejs/performance

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Apr 15, 2026
@justjake
Copy link
Copy Markdown

justjake commented Apr 15, 2026

Great! Thank you. It looks like I won't need my own unsafe-pointer once node:ffi gets stabilized 🎉 😄

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 15, 2026

Codecov Report

❌ Patch coverage is 60.71429% with 88 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.68%. Comparing base (d7ab027) to head (d33227e).
⚠️ Report is 25 commits behind head on main.

Files with missing lines Patch % Lines
src/ffi/data.cc 49.48% 33 Missing and 16 partials ⚠️
src/node_ffi.cc 46.80% 24 Missing and 1 partial ⚠️
src/ffi/types.cc 57.57% 14 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #62762      +/-   ##
==========================================
- Coverage   89.69%   89.68%   -0.01%     
==========================================
  Files         706      706              
  Lines      218143   218400     +257     
  Branches    41730    41805      +75     
==========================================
+ Hits       195655   195864     +209     
- Misses      14401    14419      +18     
- Partials     8087     8117      +30     
Files with missing lines Coverage Δ
lib/ffi.js 96.56% <100.00%> (+0.71%) ⬆️
src/node_errors.h 86.48% <ø> (ø)
src/node_ffi.h 72.72% <ø> (ø)
src/ffi/types.cc 59.61% <57.57%> (-0.06%) ⬇️
src/node_ffi.cc 69.04% <46.80%> (-0.04%) ⬇️
src/ffi/data.cc 73.28% <49.48%> (-5.50%) ⬇️

... and 41 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@meixg
Copy link
Copy Markdown
Member

meixg commented Apr 16, 2026

LGTM
One small API thought: would it be better to expose this as something like getRawReference() instead of getRawPointer(), returning both the pointer and byteLength? For byte sources, the pointer is usually
meant to be used together with its length.

@ShogunPanda
Copy link
Copy Markdown
Contributor Author

@meixg I don't think it is acually necessary. When using the function you already own the source (getRawPointer(buffer)) so you can call the byteLength directly, isn't it?

@ShogunPanda ShogunPanda changed the title ffi: added new methods and error codes ffi: add new methods and error codes Apr 16, 2026
Signed-off-by: Paolo Insogna <paolo@cowtech.it>
Comment thread src/ffi/data.cc Outdated
Comment on lines +769 to +771
if (Buffer::HasInstance(args[0])) {
ptr = reinterpret_cast<uintptr_t>(Buffer::Data(args[0]));
} else if (args[0]->IsArrayBuffer()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already covered by the IsArrayBufferView branch below

Suggested change
if (Buffer::HasInstance(args[0])) {
ptr = reinterpret_cast<uintptr_t>(Buffer::Data(args[0]));
} else if (args[0]->IsArrayBuffer()) {
if (args[0]->IsArrayBuffer()) {

Comment thread src/ffi/types.cc Outdated
std::memchr(*value, '\0', value.length()) != nullptr) {
env->ThrowTypeError((label + " must not contain null bytes").c_str());
THROW_ERR_INVALID_ARG_VALUE(
env, (label + " must not contain null bytes").c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
env, (label + " must not contain null bytes").c_str());
env, "%s must not contain null bytes", label);

Ideally, this would be applied to all other formatting operations too

Comment thread src/ffi/types.cc Outdated
" must have either 'returns', 'return' or 'result' "
"property";
env->ThrowTypeError(msg.c_str());
THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
THROW_ERR_INVALID_ARG_VALUE(env, msg);

Comment thread src/ffi/types.cc Outdated
" must have either 'parameters' or 'arguments' "
"property";
env->ThrowTypeError(msg.c_str());
THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
THROW_ERR_INVALID_ARG_VALUE(env, msg);

Comment thread src/ffi/types.cc Outdated
std::string msg =
"Return value type of function " + name + " must be a string";
env->ThrowTypeError(msg.c_str());
THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
THROW_ERR_INVALID_ARG_VALUE(env, msg);

Comment thread src/ffi/types.cc Outdated
Comment on lines 469 to 471
("Argument " + std::to_string(index) +
" must be a buffer, an ArrayBuffer, a string, or a bigint")
.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
("Argument " + std::to_string(index) +
" must be a buffer, an ArrayBuffer, a string, or a bigint")
.c_str());
"Argument %s"
" must be a buffer, an ArrayBuffer, a string, or a bigint", index);

Comment thread src/ffi/types.cc Outdated
Comment on lines +459 to +461
("Argument " + std::to_string(index) +
" must be a non-negative pointer bigint")
.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
("Argument " + std::to_string(index) +
" must be a non-negative pointer bigint")
.c_str());
"Argument %s"
" must be a non-negative pointer bigint", index);

Comment thread src/ffi/types.cc Outdated
Comment on lines +446 to +448
("Invalid ArrayBuffer backing store for argument " +
std::to_string(index))
.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
("Invalid ArrayBuffer backing store for argument " +
std::to_string(index))
.c_str());
"Invalid ArrayBuffer backing store for argument %s",
index);

Comment thread src/ffi/types.cc Outdated
Comment on lines 424 to 426
("Invalid ArrayBufferView backing store for argument " +
std::to_string(index))
.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
("Invalid ArrayBufferView backing store for argument " +
std::to_string(index))
.c_str());
"Invalid ArrayBufferView backing store for argument %s", index);

Comment thread src/ffi/data.cc Outdated
Comment on lines +690 to +693
if (Buffer::HasInstance(args[0])) {
source_data = reinterpret_cast<uint8_t*>(Buffer::Data(args[0]));
source_len = Buffer::Length(args[0]);
} else if (args[0]->IsArrayBuffer()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto here

Suggested change
if (Buffer::HasInstance(args[0])) {
source_data = reinterpret_cast<uint8_t*>(Buffer::Data(args[0]));
source_len = Buffer::Length(args[0]);
} else if (args[0]->IsArrayBuffer()) {
if (args[0]->IsArrayBuffer()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And more broadly than that, you can probably just implement this a lot more easily by using ArrayBufferViewContents

Comment thread src/node_ffi.cc Outdated
return;
}
library_path = *filename;
lib->path_ = std::string(*filename);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lib->path_ = std::string(*filename);
lib->path_ = filename.ToString();

Comment thread src/node_ffi.cc Outdated
if (ThrowIfContainsNullBytes(env, filename, "Library path")) {
return;
}
library_path = *filename;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a use-after-free bug – *filename goes out of scope outside of this conditional

Comment thread src/ffi/data.cc Outdated
env->ThrowRangeError(
(std::string("The ") + label + " is too large").c_str());
THROW_ERR_OUT_OF_RANGE(
env, (std::string("The ") + label + " is too large").c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
env, (std::string("The ") + label + " is too large").c_str());
"The %s is too large", label);

Comment thread src/ffi/data.cc Outdated
Comment on lines 87 to 88
(std::string("The ") + label + " exceeds the platform pointer range")
.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(std::string("The ") + label + " exceeds the platform pointer range")
.c_str());
"The %s exceeds the platform pointer range", label);

Comment thread src/ffi/data.cc Outdated
Comment on lines +690 to +693
if (Buffer::HasInstance(args[0])) {
source_data = reinterpret_cast<uint8_t*>(Buffer::Data(args[0]));
source_len = Buffer::Length(args[0]);
} else if (args[0]->IsArrayBuffer()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And more broadly than that, you can probably just implement this a lot more easily by using ArrayBufferViewContents

Signed-off-by: Paolo Insogna <paolo@cowtech.it>
@ShogunPanda
Copy link
Copy Markdown
Contributor Author

@addaleax I think I addressed all your suggestions. Do you mind checking it again?

@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Copy link
Copy Markdown
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Comment thread doc/api/errors.md

<a id="ERR_FFI_SYSCALL_FAILED"></a>

### `ERR_FFI_SYSCALL_FAILED`
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say this is a bad name unless it actually has anything to do with syscalls, which is a pretty specific pre-existing term

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, I made other comments assuming that FFI_SYSCALL, though poorly named, should be taken to be something more like FFI_CALL, but instead it seems like it's being used as a more generic error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find another example in errors.md, but maybe ERR_FFI_ERROR is generic enough? Otherwise, being specific for each of the error cases might be in order.

Comment thread src/ffi/data.cc
THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBufferView backing store");
return;
}
source_data = const_cast<uint8_t*>(view.data());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This const_cast should not be necessary if source_data is a const uint8_t* (which it should be anyway)

Comment thread src/ffi/data.cc
return;
}

uintptr_t ptr = 0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code makes the assumption that a BackingStore will never move even if no references are held by embedders; I am not sure if this is something that V8 guarantees, or something that we should rely on.

We can add this as experimental but we really, really need to evaluate whether this is an acceptable model that is guaranteed to be usable in the long term or not

Comment thread src/node_ffi.cc
if (uv_dlsym(&lib_, name.c_str(), &ptr) != 0) {
std::string msg = std::string("dlsym failed: ") + uv_dlerror(&lib_);
env->ThrowError(msg.c_str());
THROW_ERR_FFI_SYSCALL_FAILED(env, msg.c_str());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not an FFI call that's failing, but the resolution of a symbol.

Comment thread src/node_ffi.cc
}

env->ThrowError(msg);
THROW_ERR_FFI_SYSCALL_FAILED(env, msg);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also not an FFI call that's failing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants